package cn.k7g.alloy.expose;

import cn.k7g.alloy.annotation.AlloyContent;
import cn.k7g.alloy.exception.AlloyContentDecodeException;
import cn.k7g.alloy.exception.AlloyContentEncodeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * 加密的内容的处理器，根据key生成内容
 *
 * @author victor-wu
 * @date 2024年4月3日
 */
@Slf4j
public abstract class AlloyContentHandler {

    public static final String DEFAULT_BEAN_NAME = "alloyContentHandler";

    @Autowired
    @Lazy
    protected ObjectMapper objectMapper;

    /**
     * 获取一个 AlloyContentHandler
     *
     * 不建议使用这个函数，随时可以能被取消
     * @param beanFactory
     * @param alloyContent
     * @return
     */
    @Deprecated
    public static AlloyContentHandler newInstance(BeanFactory beanFactory, AlloyContent alloyContent) {
        if (AlloyContentHandler.DEFAULT_BEAN_NAME.equals(alloyContent.value())) {
            return beanFactory.getBean(AlloyContentHandler.class);
        } else {
            return (AlloyContentHandler) beanFactory.getBean(alloyContent.value());
        }
    }

    /**
     * 返回一个匿名情况下的 key，这个通常是固定的一个值,
     * 在没有登录的情况下用户都将使用这个key
     *
     * @return
     */
    public abstract byte[] anonymousKey();

    /**
     * 数据拥有者，一般为用户ID 或者 租户ID 商户ID等，不可超出32长度
     *
     * @return
     */
    public abstract byte[] ownerKey();

    /**
     * 是否登录，如果已经登录就用  ownerKey  否则就是 anonymousOwnerKey
     *
     * @return
     */
    public abstract boolean isLogin();

    /**
     * 填充KEY，超出范围截断
     *
     * @return 返回用于加密的key
     */
    public byte[] safeKey(byte[] key) {
        if (key.length > 32) {
            throw new RuntimeException("alloy key 长度不能超过32");
        }
        if (key.length == 16 || key.length == 24 || key.length == 32) {
            return key;
        }
        if (key.length < 16) {
            return fill(key, 16);
        }
        if (key.length < 24) {
            return fill(key, 24);
        }
        return fill(key, 32);
    }

    public Cipher getCipherEncode() {
        byte[] key = isLogin() ? ownerKey() : anonymousKey();
        return createCipher(Cipher.ENCRYPT_MODE, key);
    }
    public Cipher getCipherDecode() {
        byte[] key = isLogin() ? ownerKey() : anonymousKey();
        return createCipher(Cipher.DECRYPT_MODE, key);
    }

    public Cipher getAnonymousCipherDecode() {
        return createCipher(Cipher.DECRYPT_MODE, anonymousKey());
    }


    private Cipher createCipher(int mode, byte[] key) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(safeKey(key), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(mode, secretKeySpec);
            return cipher;
        } catch (InvalidKeyException e) {
            // 在 safeKey 中已经得到解决了, 这里不会再触发了
            throw new RuntimeException(e);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            // 这直接抛出就行了，通常是缺少包造成的
            throw new RuntimeException(e);
        }
    }

    public byte[] fill(byte[] key, int len) {
        byte[] nk = new byte[len];
        System.arraycopy(key, 0, nk, 0, key.length);
        for (int i = key.length; i < nk.length; i++) {
            nk[i] = '0';
        }
        return nk;
    }

    /**
     * 解密, 返回解码后的明文
     *
     * @param value 密文
     * @param cipher   密码
     * @return 返回一个解密后的内容
     */
    public static String decode(String value, Cipher cipher) throws AlloyContentDecodeException {
        try {
            byte[] decryptedBytes = cipher.doFinal(Base64.getUrlDecoder().decode(value));
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new AlloyContentDecodeException("无效的参数数据", e);
        }
    }

    /**
     * 加密， 返回加密后的内容
     *
     * @param originId 原始数据ID
     * @param cipher      密码
     * @return 返回一个加密后的内容
     */
    public static String encode(String originId, Cipher cipher) throws AlloyContentEncodeException {
        try {
            byte[] encryptedBytes = cipher.doFinal(originId.getBytes(StandardCharsets.UTF_8));
            return Base64.getUrlEncoder().withoutPadding().encodeToString(encryptedBytes);
        } catch (Exception e) {
            throw new AlloyContentEncodeException("加密失败", e);
        }
    }


    /**
     * 将AlloyContent转化成对象
     *
     * @param content  密文
     * @param dataType 转化类型
     * @return 解密后的对象
     * @throws AlloyContentDecodeException json解析错误
     */
    public <T> T decodeObject(String content, Class<T> dataType) throws AlloyContentDecodeException {
        try {
            String originContent;
            try {
                originContent = decode(content, getCipherDecode());
            } catch (Exception e) {
                // 如果是已经登录的情况下解密失败，尝试用一次匿名解析
                if (isLogin()) {
                    originContent = decode(content, getAnonymousCipherDecode());
                } else {
                    throw e;
                }
            }
            return objectMapper.readValue(originContent, dataType);
        } catch (JsonProcessingException e) {
            throw new AlloyContentDecodeException("JSON数据格式错误", e);
        }
    }



    /**
     * 将对象值转化为 AlloyContent
     *
     * @param o
     * @return 返回加密后的内容
     * @throws AlloyContentEncodeException 加密识别
     */
    public String encodeObject(Object o) throws AlloyContentEncodeException {
        try {
            return encode(objectMapper.writeValueAsString(o), getCipherEncode());
        } catch (JsonProcessingException e) {
            throw new AlloyContentEncodeException("数据无法转换为json格式", e);
        }
    }
}
